package com.metaaps.eoraster;

import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;

import com.sun.media.imageio.plugins.tiff.TIFFImageReadParam;

/**
 * A class that reads Radarsat 2 images (geotiffs + xml). simple to quadri polarisation are supported
 * @author thoorfr
 */
public class Radarsat2Image extends SARImageReader {

    protected TIFF image;
    protected String[] files;
    protected int[] preloadedInterval = new int[]{0, 0};
    protected int[] preloadedData;
    protected Rectangle bounds;
    protected File productxml;
    protected Document doc;
    protected Namespace ns = Namespace.getNamespace("http://www.rsi.ca/rs2/prod/xml/schemas");
    protected Map<String, TIFF> tiffImages;
    protected Vector<String> bands;
    protected String timestampStart;
    protected String timestampStop;
    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,id=DCE.4A46CCF6-E120-452D-098F-D6C37C7B5C56]
    // </editor-fold> 
    public Radarsat2Image() {
    }

    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,id=DCE.9B52B7CE-5899-1EA9-B51A-06AF9276B5FF]
    // </editor-fold> 
    @Override
    public int getNBand() {
        return bands.size();
    }

    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,id=DCE.CE6FA7B0-9AE0-5671-FC90-98F878DAE574]
    // </editor-fold> 
    @Override
    public List<GCP> getGcps() {
        if (gcps == null) {
            parseProductXML(productxml);
        }
        return gcps;
    }

    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,id=DCE.36280E4D-CE78-E42B-818E-109BBA554A95]
    // </editor-fold> 
    @Override
    public String getAccessRights() {
        return "r";
    }

    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,id=DCE.D0A504CE-05E4-8581-2386-5B1FF17389F5]
    // </editor-fold> 
    @Override
    public String[] getFilesList() {
        return files;
    }

    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,id=DCE.C96C2CB4-F58B-E9CA-5017-A116AD5A64C4]
    // </editor-fold> 
    @Override
    public boolean initialise() {
        if(tiffImages==null) return false;
        if(getMetadata(PRODUCT).equals("SLC")){
                return false;
            }
        try {
            image = tiffImages.values().iterator().next();
            xSize = image.reader.getWidth(0);
            ySize = image.reader.getHeight(0);
            bounds = new Rectangle(0, 0, xSize, ySize);
            
        } catch (IOException ex) {
            dispose();
            //Logger.getLogger(Radarsat2Image.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
        return true;
    }

    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,id=DCE.036FA74B-6D3C-1A54-161C-9A37185035C1]
    // </editor-fold> 
    @Override
    public void setFile(File imageFile) {
        files = new String[1];
        try {
            File parent = imageFile;
            if (!parent.isDirectory()) {
                parent = imageFile.getParentFile();
            }
            for (File f : parent.listFiles()) {
                if (f.getName().endsWith("product.xml")) {
                    productxml = f;
                    files[0]=f.getAbsolutePath();
                }
            }
            if (productxml != null) {
                parseProductXML(productxml);
            } else {
                throw new IOException("Cannot find product.xml");
            }

            tiffImages = getImages();

            String mm = imageFile.getAbsolutePath();
        } catch (IOException ex) {
            dispose();
            //Logger.getLogger(Radarsat2Image.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    @Override
    public int[] readTile(int x, int y, int width, int height) {
        Rectangle rect = new Rectangle(x, y, width, height);
        rect = rect.intersection(bounds);
        int[] tile= new int[height*width];
        if (rect.isEmpty()) {
            return tile;
        }
        if (rect.y != preloadedInterval[0] | rect.y + rect.height != preloadedInterval[1]) {
            preloadLineTile(rect.y, rect.height);
        }
        int yOffset = xSize;
        int xinit = rect.x - x;
        int yinit = rect.y - y;
        for (int i = 0; i < rect.height; i++) {
            for (int j = 0; j < rect.width; j++) {
                int temp = i * yOffset + j + rect.x;
                tile[(i + yinit) * width + j + xinit] = preloadedData[temp];
            }
        }
        return tile;
    }
/*
    @Override
    public IntTile readAndDecimateTile(int x, int y, int width, int height, int outWidth, int outHeight, boolean filter) {
        IntTile out = new IntTile(x, y, outWidth, outHeight);
        if (height < 257) {
            int[] outData = new int[outWidth * outHeight];
            for (int i = 0; i < outData.length; i++) {
                outData[i] = 0;
            }
            IntTile t = readTile(x, y, width, height);
            int decX = (int) Math.floor(width / (1f * outWidth));
            int decY = (int) Math.floor(height / (1f * outHeight));
            int[] data = t.getData();
            if (data != null) {
                int index = 0;
                for (int j = 0; j < outHeight; j++) {
                    int temp = (int) (j * decY) * width;
                    for (int i = 0; i < outWidth; i++) {
                        if (filter) {
                            for (int h = 0; h < decY; h++) {
                                for (int w = 0; w < decX; w++) {
                                    outData[index] += data[temp + h * width + (int) (i * decX + w)];
                                }
                            }
                            if (decX > 1) {
                                outData[index] /= decX;
                            }
                            if (decY > 1) {
                                outData[index] /= decY;
                            }
                        } else {
                            outData[index] = data[temp + (int) (i * decX)];
                        }
                        index++;
                    }
                }
            }
            out.setData(outData);
        } else {
            float incy = height / 256f;
            int[] outData = new int[outWidth * outHeight];
            for (int i = 0; i < outData.length; i++) {
                outData[i] = 0;
            }
            float decY = height / (1f * outHeight);
            float decX = width / (1f * outWidth);
            int index = 0;
            for (int i = 0; i < Math.ceil(incy); i++) {
                int tileHeight = Math.min(256, height - i * 256);
                if (tileHeight > decY) {
                    int[] temp = readAndDecimateTile(x, y + i * 256, width, tileHeight, outWidth, Math.round(tileHeight / decY), filter).getData();
                    if (temp != null) {
                        for (int j = 0; j < temp.length; j++) {
                            if (index < outData.length) {
                                outData[index++] = temp[j];
                            }
                        }
                    } else {
                        index += outWidth * (int) (256 / decY);
                    }
                }
            }
            out.setData(outData);
        }
        return out;
    }
 * 
 * */

    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,id=DCE.091BEB3C-7DA3-1625-A9FA-3CED510139EC]
    // </editor-fold> 
    @Override
    public int read(int x, int y) {
        TIFFImageReadParam t=new TIFFImageReadParam();
        t.setSourceRegion(new Rectangle(x, y, 1, 1));
        try {
            return image.reader.read(0, t).getRGB(x, y);            
        } catch (IOException ex) {
            Logger.getLogger(GeotiffImage.class.getName()).log(Level.SEVERE, null, ex);
        }
        return -1;
        
    }

    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,id=DCE.A8D91FF2-4D12-67A3-4D11-AB34FEA457C7]
    // </editor-fold> 
    @Override
    public String getBandName(int band) {
        return bands.get(band);
    }

    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,id=DCE.F95FC6F1-B536-4D94-DDED-07B9C0FA48D9]
    // </editor-fold> 
    @Override
    public void setBand(int band) {
        this.band=band;
        this.image=tiffImages.get(bands.get(band));
    }

    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,id=DCE.A95D94A3-4A56-4C07-ACBE-58BE24845725]
    // </editor-fold> 
    @Override
    public void preloadLineTile(int y, int length) {
        if (y < 0) {
            return;
        }
        preloadedInterval = new int[]{y, y + length};
        Rectangle rect = new Rectangle(0, y, xSize, length);
        TIFFImageReadParam tirp=new TIFFImageReadParam();
        tirp.setSourceRegion(rect);
        try {
            preloadedData = image.reader.read(0, tirp).getRaster().getSamples(0, 0, xSize, length, 0, (int[]) null);
        } catch (Exception ex) {
            Logger.getLogger(GeotiffImage.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    // <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
    // #[regen=yes,id=DCE.EE5E26F6-3694-26D5-5C48-4609D842A12E]
    // </editor-fold> 
    @Override
    public void dispose() {
        super.dispose();
        if(tiffImages==null) return;
        for(TIFF t:tiffImages.values()){
            t.dispose();
        }
        tiffImages=null;
    }

    private Map<String, TIFF> getImages() {
        List elements = doc.getRootElement().getChild("imageAttributes", ns).getChildren("fullResolutionImageData", ns);
        Map<String, TIFF> tiffs = new HashMap<String, TIFF>();
        bands=new Vector<String>();
        for (Object o : elements) {
            if (o instanceof Element) {
                File f = new File(productxml.getParent(), ((Element) o).getText());
                String polarisation = ((Element) o).getAttribute("pole").getValue();
                tiffs.put(polarisation, new TIFF(f));
                bands.add(polarisation);
            }
        }
        return tiffs;
    }

    private void parseProductXML(File productxml) {
        try {
            gcps = new ArrayList<GCP>();

            setMetadata(SATELLITE, "RADARSAT-2");
            setMetadata(SENSOR, "SAR Payload Module");
            
            SAXBuilder builder = new SAXBuilder();
            doc = builder.build(productxml);
            Element atts = doc.getRootElement().getChild("imageGenerationParameters", ns);
            
            // generalprocessinginformation
            atts = atts.getChild("generalProcessingInformation", ns);
            setMetadataXML(atts, "productType", PRODUCT, ns);
            setMetadataXML(atts, "processingFacility", PROCESSOR, ns);
            
            // geolocationgrid
            atts = doc.getRootElement().getChild("imageAttributes", ns);
            atts = atts.getChild("geographicInformation", ns);
            atts = atts.getChild("geolocationGrid", ns);
            for (Object o : atts.getChildren("imageTiePoint", ns)) {
                GCP gcp = new GCP();
                Element elem = (Element) o;
                gcp.setXpix(Double.parseDouble(elem.getChild("imageCoordinate", ns).getChild("pixel", ns).getText()));
                gcp.setYpix(Double.parseDouble(elem.getChild("imageCoordinate", ns).getChild("line", ns).getText()));
                gcp.setXgeo(Double.parseDouble(elem.getChild("geodeticCoordinate", ns).getChild("longitude", ns).getText()));
                gcp.setYgeo(Double.parseDouble(elem.getChild("geodeticCoordinate", ns).getChild("latitude", ns).getText()));
                gcp.setZgeo(Double.parseDouble(elem.getChild("geodeticCoordinate", ns).getChild("height", ns).getText()));
                gcps.add(gcp);
            }
            
            String epsg = "EPSG:4326";
            geotransform = GeoTransformFactory.createFromGcps(gcps, epsg);
            
            // check if ascending or descending
            boolean ascending = doc.getRootElement().getChild("sourceAttributes", ns).getChild("orbitAndAttitude", ns).getChild("orbitInformation", ns).getChild("passDirection", ns).getText().equalsIgnoreCase("Ascending");
            setMetadata(ORBIT_DIRECTION, ascending ? new String("ASCENDING") : new String("DESCENDING"));
            Element sarProcessingInformation = doc.getRootElement().getChild("imageGenerationParameters", ns).getChild("sarProcessingInformation", ns);
            // use different values if ascending or descending
            if(ascending)
            {
                timestampStop = (String)sarProcessingInformation.getChild("zeroDopplerTimeFirstLine", ns).getText();
                timestampStart = (String)sarProcessingInformation.getChild("zeroDopplerTimeLastLine", ns).getText();
            } else {
                timestampStart = (String)sarProcessingInformation.getChild("zeroDopplerTimeFirstLine", ns).getText();
                timestampStop = (String)sarProcessingInformation.getChild("zeroDopplerTimeLastLine", ns).getText();
            }
            // change the string to match Timestamp format
            timestampStart = timestampStart.replaceAll("T", " ");
            timestampStart = timestampStart.replaceAll("Z", " ");
            //timestampStart = timestampStart.substring(0, timestampStart.lastIndexOf(":") + 3) + "." + timestampStart.substring(timestampStart.lastIndexOf(":") + 3);
            timestampStop = timestampStop.replaceAll("T", " ");
            timestampStop = timestampStop.replaceAll("Z", " ");
            //timestampStop = timestampStop.substring(0, timestampStop.lastIndexOf(":") + 3) + "." + timestampStop.substring(timestampStop.lastIndexOf(":") + 3);
            
            setMetadata(TIMESTAMP_START, timestampStart);
            setMetadata(TIMESTAMP_STOP, timestampStop);
            
            atts = doc.getRootElement().getChild("sourceAttributes", ns);
            atts = atts.getChild("radarParameters", ns);
            setMetadataXML(atts, "beams", BEAM, ns);
            setMetadataXML(atts, "acquisitionType", MODE, ns);
            setMetadataXML(atts, "polarizations", POLARISATION, ns);
            setMetadataXML(atts, "antennaPointing", LOOK_DIRECTION, ns);
            setMetadataXML(atts, "pulseRepetitionFrequency", PRF, ns);
            setMetadataXML(atts, "radarCenterFrequency", RADAR_WAVELENGTH, ns);
            //convert to wavelength
            double radarFrequency = Double.parseDouble((String) getMetadata(RADAR_WAVELENGTH));
            setMetadata(RADAR_WAVELENGTH, String.valueOf(299792457.9 / radarFrequency));

            // sarprocessinginformation
            atts = doc.getRootElement().getChild("imageGenerationParameters", ns).getChild("sarProcessingInformation", ns);
            setMetadataXML(atts, "numberOfAzimuthLooks", ENL, ns);
            setMetadataXML(atts, "incidenceAngleNearRange", INCIDENCE_NEAR, ns);
            setMetadataXML(atts, "incidenceAngleFarRange", INCIDENCE_FAR, ns);
            setMetadataXML(atts, "satelliteHeight", SATELLITE_ALTITUDE, ns);
            
            // orbitandattitude
            atts = doc.getRootElement().getChild("sourceAttributes", ns).getChild("orbitAndAttitude", ns).getChild("orbitInformation", ns);
            setMetadataXML(atts, "passDirection", ORBIT_DIRECTION, ns);
            // calculate satellite speed using state vectors
            atts = atts.getChild("stateVector", ns);
            if(atts != null)
            {
                double xvelocity = Double.valueOf(atts.getChildText("xVelocity", ns));
                double yvelocity = Double.valueOf(atts.getChildText("yVelocity", ns));
                double zvelocity = Double.valueOf(atts.getChildText("zVelocity", ns));
                double satellite_speed = Math.pow(Math.pow(xvelocity, 2) + Math.pow(yvelocity, 2) + Math.pow(zvelocity, 2), 0.5);
                setMetadata(SATELLITE_SPEED, String.valueOf(satellite_speed));
            }
            
            // rasterattributes
            atts = doc.getRootElement().getChild("imageAttributes", ns).getChild("rasterAttributes", ns); 
            if(atts.getChild("numberOfLines", ns) != null)
                setMetadata(HEIGHT,Integer.parseInt(atts.getChild("numberOfLines", ns).getText()));
            if(atts.getChild("numberOfSamplesPerLine", ns) != null)
                setMetadata(WIDTH,Integer.parseInt(atts.getChild("numberOfSamplesPerLine", ns).getText()));
            if(atts.getChild("bitsPerSample", ns) != null)
                setMetadata(NUMBER_BYTES,Integer.parseInt(atts.getChild("bitsPerSample", ns).getText())/8);
            setMetadataXML(atts, "sampledPixelSpacing", AZIMUTH_SPACING, ns);
            setMetadataXML(atts, "sampledPixelSpacing", RANGE_SPACING, ns);

            setMetadata(HEADING_ANGLE, String.valueOf(this.getImageAzimuth()));
            setMetadata(SATELLITE_ORBITINCLINATION, "98.5795");
            setMetadata(REVOLUTIONS_PERDAY, (new Double(14 + 7/34)).toString());
            setMetadata(K, new String("0.0"));

        } catch (JDOMException ex) {
            Logger.getLogger(Radarsat2Image.class.getName()).log(Level.SEVERE, null, ex);
        } catch (Exception ex) {
            Logger.getLogger(Radarsat2Image.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void setMetadataXML(Element atts, String attribute, String metadata, Namespace namespace)
    {
        if((atts != null) && (atts.getChild(attribute, namespace) != null))
            setMetadata(metadata, atts.getChild(attribute, namespace).getText());
    }
    
    @Override
    public int getNumberOfBytes() {
        return 2;
    }

    @Override
    public int getType(boolean oneBand) {
        if(oneBand || bands.size()<2) return BufferedImage.TYPE_USHORT_GRAY;
        else return BufferedImage.TYPE_INT_RGB;
    }

    @Override
    public String getFormat() {
        return getClass().getCanonicalName();
    }
}

